#! /usr/bin/perl

# Syshogs.pm
# Originally "pigs" from COMP 150PPP: Perl Programming Practicum (taught by Professor Alva Couch)
# Tufts University Department of Computer Science
# Written by Ming Chow
# Last updated on September 10, 2006

package Syshogs;
require Exporter;
use strict;

BEGIN 
{
	@Syshogs::ISA = ('Exporter');
	@Syshogs::EXPORT = ();
	# List of optional names to export
	@Syshogs::EXPORT_OK = ();
}

sub accumulate
{
	my $procs = shift;
	my @list;

	opendir(PROC, "/proc") or die "Can't open /proc: $!";
	my @dirs = (readdir(PROC));
	closedir PROC;

	# Find legitimate PIDs
	foreach my $pid (@dirs)
	{
		if ($pid =~ /^[0-9]+$/)
		{
			my @status = ();
			my @maps = ();
			my @stat = ();
			@list = ();
			my $file_name = "/proc/$pid/status";

			# Do error checking in case of a process in list dies suddenly
			if (open(INFILE, "<$file_name"))
			{
				@status = <INFILE>;
				close(INFILE);
				foreach (@status)
				{
					if ($_ =~ /^Name:/)
					{
						@list = split(/\s+/, $_);
						$procs->{"$pid"}->{'pname'} = $list[1];
					}
					elsif ($_ =~ /^Uid:/)
					{
						@list = split(/\s+/, $_);
						$procs->{"$pid"}->{'uid'} = $list[1];
					}
					elsif ($_ =~ /^VmSize:/)
					{
						@list = split(/\s+/, $_);

						# If a process's memory increases, update it (for '-c' option)
						if ($list[1] > $procs->{"$pid"}->{'VmSize'}) {
							$procs->{"$pid"}->{'VmSize'} = $list[1];
						}
					}
				}
			}
			$file_name = "/proc/$pid/stat";
			if (open(INFILE, "<$file_name"))
			{
				@list = ();
				@list = split(/\s+/, <INFILE>);
				close(INFILE);
				$procs->{"$pid"}->{'jiffies'} = $list[13] + $list[14];
			}
			$file_name = "/proc/$pid/maps";
			if (open(INFILE, "<$file_name"))
			{
				@maps = <INFILE>;
				close(INFILE);
				foreach (@maps)
				{
					(my $address, my $perms, my $offset, my $dev, my $inode, my $pathname) = split('\s+', $_);
					# Now this is the way to calculate the true memory usage:
					# 	(my $addr1, my $addr2) = split('-', $address);
					# 	$sum += hex($addr2) - hex($addr1);
					# But I am not going to do this because non-root users may have problems reading the maps file (alas, memory = undef or 0)
					$procs->{"$pid"}->{'dependencies'}->{"$pathname"} = 1 if ($pathname ne '' && !exists($procs->{"$pid"}->{'dependencies'}->{"$pathname"}));
				}
			}
		}
	}
	return $procs;
}

sub organize_by_user
{
	my $procs = shift;
	my $users = {};
	foreach my $pid (keys %$procs)
	{
		my $element = {'pid' => $pid, 'VmSize' => $procs->{$pid}->{'VmSize'}, 'pname' => $procs->{$pid}->{'pname'}, 'jiffies' => $procs->{$pid}->{'jiffies'}, 'dependencies' => $procs->{$pid}->{'dependencies'}};
		push (@{$users->{$procs->{$pid}->{'uid'}}}, $element);
	}
	return $users;
}

sub jiffies2seconds
{
	my $jiffies = shift;
	my $secs = int $jiffies/100;
	my $HH = int $secs/3600;
	my $secs = $secs%60;
	my $MM = int $secs/60;
	my $SS = $secs%60;
	if ($HH < 10) {
		$HH = "0$HH";
	}
	if ($MM < 10) {
		$MM = "0$MM";
	}
	if ($SS < 10) {
		$SS = "0$SS";
	}
	return "$HH:$MM:$SS";
}

sub summary
{
	# Render the results of a summary on STDOUT
	my $compressed = shift;
	my $dump = {};
	my $summary;
	foreach my $uid (keys %{$compressed})
	{
		foreach my $element (@{$compressed->{$uid}})
		{
			$dump->{$uid}->{'cpu_seconds'} += $element->{'jiffies'};
			$dump->{$uid}->{'memory'} += $element->{'VmSize'};
			$dump->{$uid}->{'processes'}++;
		}
	}
 	foreach my $uid (keys %{$dump})
	{
		# Convert uid to login and shell names (slow way)
		my ($login, $passwd, $uid, $giid, $gcos, $home, $shell) = getpwuid($uid);
		push(@$summary, {'login' => $login, 'shell' => $shell, 'processes' => $dump->{$uid}->{'processes'}, 'cpu_seconds' => jiffies2seconds($dump->{$uid}->{'cpu_seconds'}), 'memory' => $dump->{$uid}->{'memory'}});
	}

	# Sort summary results by greatest CPU time (and memory in case of a tie)
	@$summary = sort {$b->{'cpu_seconds'} cmp $a->{'cpu_seconds'} || $b->{'memory'} <=> $a->{'memory'}} (@$summary);
	return $summary;
}

sub render
{
	my $summary = shift;
	my $login;
	my $shell;
	my $processes;
	my $cpu_seconds;
	my $memory;
 	print "login      | full_name               | processes | cpu_seconds | memory (k) \n";
	print "-----------+-------------------------+-----------+-------------+------------\n";
	format STDOUT=
@<<<<<<<<< | @<<<<<<<<<<<<<<<<<<<<<<<| @>>>>>>>> | @>>>>>>>>>> |  @>>>>>>>>
$login, $shell, $processes, $cpu_seconds, $memory
.
	;
	foreach (@$summary)
	{
		$login = $_->{'login'};
		$shell = $_->{'shell'};
		$processes = $_->{'processes'};
 		$cpu_seconds = $_->{'cpu_seconds'};
		$memory = $_->{'memory'};
		write();
	}
}

1;
